import { addDomainToVercel } from "@/lib/api/domains/add-domain-vercel";
import { getDomainOrThrow } from "@/lib/api/domains/get-domain-or-throw";
import { markDomainAsDeleted } from "@/lib/api/domains/mark-domain-deleted";
import { queueDomainUpdate } from "@/lib/api/domains/queue-domain-update";
import { removeDomainFromVercel } from "@/lib/api/domains/remove-domain-vercel";
import { transformDomain } from "@/lib/api/domains/transform-domain";
import { validateDomain } from "@/lib/api/domains/utils";
import { DubApiError } from "@/lib/api/errors";
import { parseRequestBody } from "@/lib/api/utils";
import { withWorkspace } from "@/lib/auth";
import { setRenewOption } from "@/lib/dynadot/set-renew-option";
import { storage } from "@/lib/storage";
import { updateDomainBodySchema } from "@/lib/zod/schemas/domains";
import { prisma } from "@dub/prisma";
import { combineWords, nanoid, R2_URL } from "@dub/utils";
import { Prisma } from "@prisma/client";
import { waitUntil } from "@vercel/functions";
import { NextResponse } from "next/server";
import { z } from "zod";

const updateDomainBodySchemaExtended = updateDomainBodySchema.extend({
  deepviewData: z.string().nullish(),
  autoRenew: z.boolean().nullish(),
});

// GET /api/domains/[domain] – get a workspace's domain
export const GET = withWorkspace(
  async ({ workspace, params }) => {
    const domainRecord = await getDomainOrThrow({
      workspace,
      domain: params.domain,
      dubDomainChecks: true,
    });

    return NextResponse.json(transformDomain(domainRecord));
  },
  {
    requiredPermissions: ["domains.read"],
  },
);

// PUT /api/domains/[domain] – edit a workspace's domain
export const PATCH = withWorkspace(
  async ({ req, workspace, params }) => {
    const {
      id: domainId,
      slug: domain,
      registeredDomain,
      logo: oldLogo,
    } = await getDomainOrThrow({
      workspace,
      domain: params.domain,
      dubDomainChecks: true,
    });

    const {
      slug: newDomain,
      placeholder,
      expiredUrl,
      notFoundUrl,
      logo,
      archived,
      assetLinks,
      appleAppSiteAssociation,
      deepviewData,
      autoRenew,
    } = await updateDomainBodySchemaExtended.parseAsync(
      await parseRequestBody(req),
    );

    if (workspace.plan === "free") {
      if (
        logo ||
        expiredUrl ||
        notFoundUrl ||
        assetLinks ||
        appleAppSiteAssociation ||
        deepviewData
      ) {
        const proFeaturesString = combineWords(
          [
            logo && "custom QR code logos",
            expiredUrl && "default expiration URLs",
            notFoundUrl && "not found URLs",
            assetLinks && "Asset Links",
            appleAppSiteAssociation && "Apple App Site Association",
            deepviewData && "Deep View",
          ].filter(Boolean) as string[],
        );

        throw new DubApiError({
          code: "forbidden",
          message: `You can only set ${proFeaturesString} on a Pro plan and above. Upgrade to Pro to use these features.`,
        });
      }
    }

    const domainUpdated =
      newDomain && newDomain.toLowerCase() !== domain.toLowerCase();

    if (domainUpdated) {
      if (registeredDomain) {
        throw new DubApiError({
          code: "forbidden",
          message: "You cannot update a Dub-provisioned domain.",
        });
      }

      const validDomain = await validateDomain(newDomain);
      if (validDomain.error && validDomain.code) {
        throw new DubApiError({
          code: validDomain.code,
          message: validDomain.error,
        });
      }

      const vercelResponse = await addDomainToVercel(newDomain);
      if (vercelResponse.error) {
        throw new DubApiError({
          code: "unprocessable_entity",
          message: vercelResponse.error.message,
        });
      }
    }

    const logoUploaded = logo
      ? await storage.upload({
          key: `domains/${domainId}/logo_${nanoid(7)}`,
          body: logo,
        })
      : null;

    // If logo is null, we want to delete the logo (explicitly set in the request body to null or "")
    const deleteLogo = logo === null && oldLogo;

    const domainRecord = await prisma.domain.update({
      where: {
        slug: domain,
      },
      data: {
        ...(domainUpdated && { slug: newDomain }),
        archived,
        placeholder,
        expiredUrl,
        notFoundUrl,
        logo: deleteLogo ? null : logoUploaded?.url || oldLogo,
        ...(assetLinks !== undefined && {
          assetLinks: assetLinks ? JSON.parse(assetLinks) : Prisma.DbNull,
        }),
        ...(appleAppSiteAssociation !== undefined && {
          appleAppSiteAssociation: appleAppSiteAssociation
            ? JSON.parse(appleAppSiteAssociation)
            : Prisma.DbNull,
        }),
        ...(deepviewData !== undefined && {
          deepviewData: deepviewData ? JSON.parse(deepviewData) : Prisma.DbNull,
        }),
      },
      include: {
        registeredDomain: true,
      },
    });

    // Sync the autoRenew setting with the registered domain
    if (registeredDomain && autoRenew !== undefined) {
      const { autoRenewalDisabledAt } = registeredDomain;

      const shouldUpdate =
        (autoRenew === false && autoRenewalDisabledAt === null) ||
        (autoRenew === true && autoRenewalDisabledAt !== null);

      if (shouldUpdate) {
        await prisma.registeredDomain.update({
          where: {
            domainId: domainId,
          },
          data: {
            autoRenewalDisabledAt: autoRenew ? null : new Date(),
          },
        });

        // only set the autoRenew option on Dynadot if it's been explicitly disabled
        if (autoRenew === false) {
          waitUntil(
            setRenewOption({
              domain,
              autoRenew,
            }),
          );
        }
      }
    }

    waitUntil(
      (async () => {
        // remove old logo
        if (oldLogo && (logo === null || logoUploaded)) {
          await storage.delete({ key: oldLogo.replace(`${R2_URL}/`, "") });
        }

        if (domainUpdated) {
          await Promise.all([
            // remove old domain from Vercel
            removeDomainFromVercel(domain),

            // trigger the queue to rename the redis keys and update the links in Tinybird
            queueDomainUpdate({
              oldDomain: domain,
              newDomain: newDomain,
            }),
          ]);
        }
      })(),
    );

    return NextResponse.json(transformDomain(domainRecord));
  },
  {
    requiredPermissions: ["domains.write"],
  },
);

// DELETE /api/domains/[domain] - delete a workspace's domain
export const DELETE = withWorkspace(
  async ({ params, workspace }) => {
    const { slug: domain, registeredDomain } = await getDomainOrThrow({
      workspace,
      domain: params.domain,
      dubDomainChecks: true,
    });

    if (registeredDomain) {
      throw new DubApiError({
        code: "forbidden",
        message: "You cannot delete a Dub-provisioned domain.",
      });
    }

    await markDomainAsDeleted({
      domain,
    });

    return NextResponse.json({ slug: domain });
  },
  {
    requiredPermissions: ["domains.write"],
  },
);
